/*
This file is part of Caelum.
See http://www.ogre3d.org/wiki/index.php/Caelum 

Copyright (c) 2006-2007 Caelum team. See Contributors.txt for details.

Caelum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Caelum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Caelum. If not, see <http://www.gnu.org/licenses/>.
*/

#include "CaelumPrecompiled.h"
#include "CaelumExceptions.h"
#include "InternalUtilities.h"
#include "PrivatePtr.h"


using namespace Caelum;


Ogre::MaterialPtr InternalUtilities::checkLoadMaterialClone(const Ogre::String& originalName, const Ogre::String& cloneName)
{
   Ogre::MaterialPtr scriptMaterial = Ogre::MaterialManager::getSingletonPtr()->getByName(originalName);
   if (scriptMaterial.isNull())
      CAELUM_THROW_UNSUPPORTED_EXCEPTION("Can't find material \"" + originalName + "\"", "Caelum");

   // Create clone
   Caelum::PrivateMaterialPtr clonedMaterial(scriptMaterial->clone(cloneName));

   // Test clone loads and there is at least on supported technique
   clonedMaterial->load();
   if (clonedMaterial->getBestTechnique () == 0)
   {
      CAELUM_THROW_UNSUPPORTED_EXCEPTION(
         "Can't load material \"" + originalName + "\": " + clonedMaterial->getUnsupportedTechniquesExplanation(),
         "Caelum");
   }

   return clonedMaterial.release();
}

Ogre::CompositorPtr InternalUtilities::checkCompositorSupported (const Ogre::String& name)
{
   Ogre::CompositorPtr comp = Ogre::CompositorManager::getSingletonPtr()->getByName(name);
   if (comp.isNull()) {
      CAELUM_THROW_UNSUPPORTED_EXCEPTION (
         "Can't find compositor \"" + name + "\"",
         "Caelum");
   }

   // Check the compositor is supported after loading.
   comp->load ();
   if (comp->getNumSupportedTechniques () == 0) {
      CAELUM_THROW_UNSUPPORTED_EXCEPTION (
         "Can't load compositor \"" + name + "\"", 
         "Caelum");
   }

   return comp;
}

void InternalUtilities::generateSphericDome (const Ogre::String &name, int segments, DomeType type)
{
   // Return now if already exists
   if (Ogre::MeshManager::getSingleton ().resourceExists (name)) {
      return;
   }

   Ogre::LogManager::getSingleton ().logMessage (
      "Caelum: Creating " + name + " sphere mesh resource...");

   // Use the mesh manager to create the mesh
   Ogre::MeshPtr msh = Ogre::MeshManager::getSingleton ().createManual (name, RESOURCE_GROUP_NAME);
   // Create a submesh
   Ogre::SubMesh *sub = msh->createSubMesh ();

   // Create the shared vertex data
   Ogre::VertexData *vertexData = new Ogre::VertexData ();
   msh->sharedVertexData = vertexData;

   // Define the vertices' format
   Ogre::VertexDeclaration *vertexDecl = vertexData->vertexDeclaration;
   size_t currOffset = 0;
   // Position
   vertexDecl->addElement (0, currOffset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
   currOffset += Ogre::VertexElement::getTypeSize (Ogre::VET_FLOAT3);
   // Normal
   vertexDecl->addElement (0, currOffset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
   currOffset += Ogre::VertexElement::getTypeSize (Ogre::VET_FLOAT3);
   // Texture coordinates
   vertexDecl->addElement (0, currOffset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0);
   currOffset += Ogre::VertexElement::getTypeSize (Ogre::VET_FLOAT2);

   // Allocate the vertex buffer
   switch (type) {
            case DT_SKY_DOME:
               vertexData->vertexCount = segments * (segments - 1) + 2;
               break;
            case DT_IMAGE_STARFIELD:
               vertexData->vertexCount = (segments + 1) * (segments + 1);
               break;
   };
   Ogre::HardwareVertexBufferSharedPtr vBuf = Ogre::HardwareBufferManager::getSingleton ().createVertexBuffer (vertexDecl->getVertexSize (0), vertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
   Ogre::VertexBufferBinding *binding = vertexData->vertexBufferBinding;
   binding->setBinding (0, vBuf);

   float *pVertex = static_cast<float *>(vBuf->lock (Ogre::HardwareBuffer::HBL_DISCARD));

   // Allocate the index buffer
   switch (type) {
            case DT_SKY_DOME:
               sub->indexData->indexCount = 2 * segments * (segments - 1) * 3;
               break;
            case DT_IMAGE_STARFIELD:
               sub->indexData->indexCount = 2 * (segments - 1) * segments * 3;
               break;
   };
   sub->indexData->indexBuffer = Ogre::HardwareBufferManager::getSingleton ().createIndexBuffer (Ogre::HardwareIndexBuffer::IT_16BIT, sub->indexData->indexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
   Ogre::HardwareIndexBufferSharedPtr iBuf = sub->indexData->indexBuffer;
   unsigned short *pIndices = static_cast<unsigned short *>(iBuf->lock (Ogre::HardwareBuffer::HBL_DISCARD));

   // Fill the buffers
   switch (type) {
            case DT_SKY_DOME:
               fillGradientsDomeBuffers (pVertex, pIndices, segments);
               break;
            case DT_IMAGE_STARFIELD:
               fillStarfieldDomeBuffers (pVertex, pIndices, segments);
               break;
   };

   // Close the vertex buffer
   vBuf->unlock ();

   // Close the index buffer
   iBuf->unlock ();

   // Finishing it...
   sub->useSharedVertices = true;
   msh->_setBounds (Ogre::AxisAlignedBox (-1, -1, -1, 1, 1, 1), false);
   msh->_setBoundingSphereRadius (1);
   msh->load ();

   Ogre::LogManager::getSingleton ().logMessage (
      "Caelum: generateSphericDome DONE");
}

void InternalUtilities::fillGradientsDomeBuffers (float *pVertex, unsigned short *pIndices, unsigned int segments)
{
   assert(segments > 0);

   const Ogre::Real deltaLatitude = Ogre::Math::PI / segments;
   const Ogre::Real deltaLongitude = Ogre::Math::TWO_PI / segments;

   // Generate the rings
   for (unsigned int i = 1; i < segments; ++i)
   {
      float r0 = (float)Ogre::Math::Sin((i * deltaLatitude));
      float y0 = (float)Ogre::Math::Cos((i * deltaLatitude));

      for (unsigned int j = 0; j < segments; ++j)
      {
         float x0 = r0 * (float)Ogre::Math::Sin((j * deltaLongitude));
         float z0 = r0 * (float)Ogre::Math::Cos((j * deltaLongitude));

         *pVertex++ = x0;
         *pVertex++ = y0;
         *pVertex++ = z0;

         *pVertex++ = -x0;
         *pVertex++ = -y0;
         *pVertex++ = -z0;

         *pVertex++ = 0.f;
         *pVertex++ = 1.f - y0;
      }
   }

   // Generate the "north pole"
   *pVertex++ = 0.f;	// Position
   *pVertex++ = 1.f;
   *pVertex++ = 0.f;
   *pVertex++ = 0.f;	// Normal
   *pVertex++ = -1.f;
   *pVertex++ = 0.f;
   *pVertex++ = 0.f;	// UV
   *pVertex++ = 0.f;

   // Generate the "south pole"
   *pVertex++ = 0.f;	// Position
   *pVertex++ = -1.f;
   *pVertex++ = 0.f;
   *pVertex++ = 0.f;	// Normal
   *pVertex++ = 1.f;
   *pVertex++ = 0.f;
   *pVertex++ = 0.f;	// UV
   *pVertex++ = 2.f;


   // Generate the mid segments
   for (unsigned int i = 0, cnt = segments - 2; i < cnt; ++i)
   {
      for (unsigned int j = 0; j < segments; ++j)
      {
         *pIndices++ = segments * i + j;
         *pIndices++ = segments * i + (j + 1) % segments;
         *pIndices++ = segments * (i + 1) + (j + 1) % segments;
         *pIndices++ = segments * i + j;
         *pIndices++ = segments * (i + 1) + (j + 1) % segments;
         *pIndices++ = segments * (i + 1) + j;
      }
   }

   // Generate the upper cap
   for (unsigned int i = 0; i < segments; i++)
   {
      *pIndices++ = segments * (segments - 1);
      *pIndices++ = (i + 1) % segments;
      *pIndices++ = i;
   }

   // Generate the lower cap
   for (unsigned int i = 0; i < segments; i++) 
   {
      *pIndices++ = segments * (segments - 1) + 1;
      *pIndices++ = segments * (segments - 2) + i;
      *pIndices++ = segments * (segments - 2) + (i + 1) % segments;
   }
}

void InternalUtilities::fillStarfieldDomeBuffers (float *pVertex, unsigned short *pIndices, unsigned int segments)
{
   assert(segments > 0);

   const float deltaLatitude = (float)Ogre::Math::PI / segments;
   const float deltaLongitude = (float)Ogre::Math::TWO_PI / segments;
   const float fInvSegments = 1.f / segments;

   // Generate the rings
   for (unsigned int i = 0; i <= segments; ++i)
   {
      float r0 = (float)Ogre::Math::Sin(i * deltaLatitude);
      float y0 = (float)Ogre::Math::Cos(i * deltaLatitude);

      for (unsigned int j = 0; j <= segments; ++j)
      {
         float x0 = r0 * (float)Ogre::Math::Sin(j * deltaLongitude);
         float z0 = r0 * (float)Ogre::Math::Cos(j * deltaLongitude);

         *pVertex++ = x0;
         *pVertex++ = y0;
         *pVertex++ = z0;

         *pVertex++ = -x0;
         *pVertex++ = -y0;
         *pVertex++ = -z0;

         *pVertex++ = j * fInvSegments;
         *pVertex++ = 1.f - (y0 * 0.5f + 0.5f);
      }
   }

   // Generate the mid segments
   unsigned int vRowSize = segments + 1;
   for (unsigned int i = 1; i < segments; ++i)
   {
      for (unsigned int j = 0; j < segments; ++j)
      {
         unsigned int baseIdx = vRowSize * i + j;
         *pIndices++ = baseIdx;
         *pIndices++ = baseIdx + 1;
         *pIndices++ = baseIdx + vRowSize + 1;
         *pIndices++ = baseIdx + 1;
         *pIndices++ = baseIdx;
         *pIndices++ = baseIdx - vRowSize;
      }
   }
}
